#include "SoundManager.h"

#include "OgreLogManager.h"

using namespace OgreAL;

// ==========================================================================
void OgreLog(const std::string& logMessage)
{
	Ogre::LogManager::getSingletonPtr()->logMessage("SoundManager: " + logMessage);	
}

// ==========================================================================
SoundManager* SoundManager::mSoundManager = 0;

// ==========================================================================
SoundManager::SoundManager(void)
{
	mIsInitialized = false;
	
	// Device and context.
	mSoundDevice = 0;
	mSoundContext = 0;

	// Audio path.
	mAudioPath = "";

	// EAX related.
	mIsEAXPresent = false;

	// Listener information - Position.
	mListenerPosition[0] = 0.0f;
	mListenerPosition[1] = 0.0f;
	mListenerPosition[2] = 0.0f;
	// Listener information - Velocity.
	mListenerVelocity[0] = 0.0f;
	mListenerVelocity[1] = 0.0f;
	mListenerVelocity[2] = 0.0f;
	// Listener information - Orientation (direction).
	mListenerOrientation[0] = 0.0f;
	mListenerOrientation[1] = 0.0f;
	mListenerOrientation[2] = -1.0f;
	// Listener information - Orientation (up).
	mListenerOrientation[3] = 0.0f;
	mListenerOrientation[4] = 1.0f;
	mListenerOrientation[5] = 0.0f;

	// Audio sources.
	mAudioSourcesInUseCount = 0;
	for (size_t i = 0; i < MAX_AUDIO_SOURCES; ++i)
	{
		mAudioSources[i] = 0;
		mAudioSourcesInUse[i] = false;
	}

	// Audio buffers.
	mAudioBuffersInUseCount = 0;
	for (size_t i = 0; i < MAX_AUDIO_BUFFERS; ++i)
	{
		mAudioBuffers[i] = 0;
		strcpy(mAudioBuffersFileName[i], "--");
		mAudioBuffersInUse[i] = false;
	}

	mSoundManager = this;

	Ogre::LogManager::getSingletonPtr()->logMessage("SoundManager created.");
}

// ==========================================================================
SoundManager::~SoundManager(void)
{
	// Delete sources and buffers.
	alDeleteSources(MAX_AUDIO_SOURCES, mAudioSources);
	alDeleteBuffers(MAX_AUDIO_BUFFERS, mAudioBuffers);

	// Destroy context and device.
	mSoundContext = alcGetCurrentContext();
	mSoundDevice = alcGetContextsDevice(mSoundContext);
	alcMakeContextCurrent(NULL);
	alcDestroyContext(mSoundContext);
	if (mSoundDevice)
		alcCloseDevice( mSoundDevice );

	alutExit();

	Ogre::LogManager::getSingletonPtr()->logMessage("SoundManager destroyed.");
}

// ==========================================================================
SoundManager* SoundManager::createManager(void)
{
	if (mSoundManager == 0)
		mSoundManager = new SoundManager();
	return mSoundManager;
}

// ==========================================================================
SoundManager* SoundManager::getSingletonPtr(void)
{
	return mSoundManager;
}

// ==========================================================================
void SoundManager::selfDestruct(void)
{
	if (getSingletonPtr())
		delete getSingletonPtr();
}

// ==========================================================================
bool SoundManager::init(void)
{	
	// Prevent a double initialization.
	if (mIsInitialized)
		return true;

	// Open an audio device.
	mSoundDevice = alcOpenDevice(NULL);

	// Check for errors.
	if (!mSoundDevice)
	{
		OgreLog("Init error. No sound device.");
		return false;
	}
	
	// Create the context.
	mSoundContext = alcCreateContext(mSoundDevice, NULL);
	
	// Check for errors.
	if (!mSoundContext)
	{
		OgreLog("Init error. No sound context.");
		return false;
	}

	// Make the context current and active.
	alcMakeContextCurrent(mSoundContext);
	if (checkALError())
	{
		OgreLog("Init error. Could not make sound context current and active.");
		return false;
	}

	// Check for EAX 2.0 support.
	mIsEAXPresent = alIsExtensionPresent("EAX2.0");
	if (mIsEAXPresent)
	{
		OgreLog("EAX2.0 available.");

#ifdef USEEAX
		eaxSet  = (EAXSet) alGetProcAddress("EAXSet");
		if (!eaxSet)
			mIsEAXPresent = false;

		eaxGet = (EAXGet) alGetProcAddress("EAXGet");
		if (!eaxGet)
			mIsEAXPresent = false;

		if (!mIsEAXPresent)
			OgreLog("Failed to get the EAX extension functions addresses.");
#else
		mIsEAXPresent = false;
		OgreLog("...but not used.");
#endif // USEEAX
	}
	else
	{
		OgreLog("EAX2.0 unavailable.");
	}

	// TODO: Test OggVorbis extension.

	// Create the sources.
	alGenSources(MAX_AUDIO_SOURCES, mAudioSources);
	if (checkALError())
		return false;

	// Create the audio buffers.
	alGenBuffers(MAX_AUDIO_BUFFERS, mAudioBuffers);
	if (checkALError())
		return false;

	// Setup the initial listener parameters.
	alListenerfv(AL_POSITION, mListenerPosition);
	alListenerfv(AL_VELOCITY, mListenerVelocity);
	alListenerfv(AL_ORIENTATION, mListenerOrientation);

	// Sound on.
	mIsSoundOn = true;

	// SoundManager fully initialized.
	mIsInitialized = true;

	OgreLog("Init complete.");

	return true;
}

// ==========================================================================
bool SoundManager::isSoundOn(void) 
{ 
	return mIsSoundOn; 
};

// ==========================================================================
void SoundManager::setAudioPath(const std::string& audioPath)
{
	mAudioPath = audioPath;
}

// ==========================================================================
bool SoundManager::checkALError(void)
{
	ALenum errorCode;
	if ((errorCode = alGetError()) != AL_NO_ERROR)
	{
		return true;
	}
	return false;
}

// ==========================================================================
std::string SoundManager::listAvailableDevices(void)
{
	std::string str = "";

	if (alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT") == AL_TRUE)
	{
		str = "List of sound devices:\n";
		str += (char*) alcGetString(NULL, ALC_DEVICE_SPECIFIER);
		str += "\n";
	}
	else
	{
		str += "Devices enumeration failed.\n";
	}

	return str;
}

// ==========================================================================
bool SoundManager::loadAudio(const std::string& filename, ALuint* audioID, bool loop)
{
	// Check if filaname is valid.
	if (filename.empty() || filename.length() > MAX_FILENAME_LENGTH)
		return false;

	if (mAudioSourcesInUseCount == MAX_AUDIO_SOURCES)
		return false; // Out of audio source slots.

	alGetError(); // Clear error code.

	// Check if the file is already into a buffer.
	int bufferID = locateAudioBuffer(filename);
	if (bufferID < 0)
	{
		// The sound file isn't loaded in a buffer, let's attempt to load it now.
		bufferID = loadAudioInToSystem(filename);
		if (bufferID < 0)
			return false; // Failed.
	}

	int sourceID = 0;
	while (mAudioSourcesInUse[sourceID])
		++sourceID;

	// Now, sourceID represents a free audio slot.
	*audioID = (ALuint) sourceID;
	mAudioSourcesInUse[sourceID] = true; // Mark this source slot as in use. 
	++mAudioSourcesInUseCount; // Bump the 'in use' counter.

	// Now inform OpenAL of the sound assignment and attach the audio buffer to the audio source.
	alSourcei(mAudioSources[sourceID], AL_BUFFER, mAudioBuffers[bufferID]);
	alSourcei(mAudioSources[sourceID], AL_LOOPING, loop);

	if (checkALError())
		return false;

	return true;
}

// ==========================================================================
int SoundManager::locateAudioBuffer(const std::string& filename)
{
	for (int b = 0; b < MAX_AUDIO_BUFFERS; ++b)
	{
		if (filename == mAudioBuffersFileName[b])
			return b;
	}
	return -1; // Not found.
}

// ==========================================================================
int SoundManager::loadAudioInToSystem(const std::string& filename)
{
	if (filename.empty())
		return -1;

	// Make sure we have audio buffers available.
	if (mAudioSourcesInUseCount == MAX_AUDIO_BUFFERS)
		return -1;

	// Find a free audio buffer slot.
	int bufferID = 0; // Sound buffer to use.

	while (mAudioBuffersInUse[bufferID] == true)
		++bufferID;

	// Now, bufferID represents a free audio buffer slot.

	if (filename.find(".wav", 0) != std::string::npos)
	{
		if (!loadWAV(filename, mAudioBuffers[bufferID]))
			return -1;
	}

	// Successful load of the file into the audio buffer.
	mAudioBuffersInUse[bufferID] = true; // Buffer now in use.
	// Save the file descriptor.
	strcpy(mAudioBuffersFileName[bufferID], filename.c_str());
	++mAudioBuffersInUseCount; // Bump the 'in use' counter.

	return bufferID;
}

// ==========================================================================
bool SoundManager::loadWAV(const std::string& filename, ALuint destAudioBuffer)
{
	ALenum		format;		// Buffer format.
	ALvoid*		data;		// Buffer data.
	ALsizei		size;		// Bit depth.
	ALsizei		freq;		// Frequency of the buffer.
	ALboolean	loop;		// Looped.

	// Set the complete path.
	std::string mFullPath = mAudioPath + filename;

	alGetError(); // Clean error code.

	// Load the wav file from disk.
	alutLoadWAVFile((ALbyte*)mFullPath.c_str(), &format, &data, &size, &freq, &loop);

	if (checkALError())
		return false;

	// Copy the new wav data into the buffer.
	alBufferData(destAudioBuffer, format, data, size, freq);
	
	if (checkALError())
		return false;

	// Unload wav file.
	alutUnloadWAV(format, data, size, freq);
	
	if (checkALError())
		return false;

	return true;
}

// ==========================================================================
bool SoundManager::playAudio(ALuint audioID, bool forceRestart)
{
	// Make sure the specified audio source is valid.
	if (audioID >= MAX_AUDIO_SOURCES || !mAudioSourcesInUse[audioID])
		return false;

	alGetError(); // Clean error code.

	ALint sourceState = 0;
	alGetSourcei(mAudioSources[audioID], AL_SOURCE_STATE, &sourceState);

	if (sourceState == AL_PLAYING)
	{
		if (forceRestart)
			stopAudio(audioID);
		else
			return false;
	}

	// Play.
	alSourcePlay(mAudioSources[audioID]);
	
	if (checkALError())
		return false;

	return true;
}

// ==========================================================================
bool SoundManager::pauseAudio(ALuint audioID)
{
	// Make sure the specified audio source is valid.
	if (audioID >= MAX_AUDIO_SOURCES || !mAudioSourcesInUse[audioID])
		return false;
	
	alGetError(); // Clean error code.

	// Pause.
	alSourcePause(mAudioSources[audioID]);

	if (checkALError())
		return false;

	return true;
}

// ==========================================================================
bool SoundManager::pauseAllAudio(void)
{
	if (mAudioSourcesInUseCount >= MAX_AUDIO_SOURCES)
		return false;

	alGetError(); // Clean error code.

	// Pause all.
	alSourcePausev(mAudioSourcesInUseCount, mAudioSources);

	if (checkALError())
		return false;

	return true;
}

// ==========================================================================
bool SoundManager::resumeAudio(ALuint audioID)
{
	// Make sure the specified audio source is valid.
	if (audioID >= MAX_AUDIO_SOURCES || !mAudioSourcesInUse[audioID])
		return false;

	alGetError(); // Clean error code.

	ALint sourceState = 0;
	alGetSourcei(audioID, AL_SOURCE_STATE, &sourceState);
	// Are we currently playing this audio source?
	if (sourceState == AL_PAUSED)
	{
		// Resume.
		alSourcePlay(mAudioSources[audioID]);	
	}

	if (checkALError())
		return false;

	return true;
}

// ==========================================================================
bool SoundManager::resumeAllAudio(void)
{
	if (mAudioSourcesInUseCount >= MAX_AUDIO_SOURCES)
		return false;

	alGetError(); // Clean error code.

	ALint sourceState = 0;

	// Resume all.
	for (size_t i = 0; i < mAudioSourcesInUseCount; ++i)
	{
		resumeAudio(i);
	}

	if (checkALError())
		return false;

	return true;
}

// ==========================================================================
bool SoundManager::stopAudio(ALuint audioID)
{
	// Make sure the specified audio source is valid.
	if (audioID >= MAX_AUDIO_SOURCES || !mAudioSourcesInUse[audioID])
		return false;

	alGetError(); // Clean error code.

	// Stop.
	alSourceStop(mAudioSources[audioID]);

	if (checkALError())
		return false;

	return true;
}

// ==========================================================================
bool SoundManager::stopAllAudio(void)
{
	if (mAudioSourcesInUseCount >= MAX_AUDIO_SOURCES)
		return false;

	alGetError(); // Clean error code.

	// Stop all.
	for (size_t i = 0; i < mAudioSourcesInUseCount; ++i)
	{
		stopAudio(i);
	}

	if (checkALError())
		return false;
	
	return true;
}

// ==========================================================================
bool SoundManager::releaseAudio(ALuint audioID)
{
	if (audioID >= MAX_AUDIO_SOURCES)
		return false;
	alSourceStop(mAudioSources[audioID]);
	mAudioSourcesInUse[audioID] = false;
	--mAudioSourcesInUseCount;
	return true;
}

// ==========================================================================
bool SoundManager::setSource(ALuint audioID, const Ogre::Vector3& sourcePosition, const Ogre::Vector3& sourceVelocity,
	const float sourceGain, const float sourcePitch)
{
	// Make sure the specified audio source is valid.
	if (audioID >= MAX_AUDIO_SOURCES || !mAudioSourcesInUse[audioID])
		return false;

	// Set source position.
	ALfloat position[] = { sourcePosition.x, sourcePosition.y, sourcePosition.z };
	alSourcefv(mAudioSources[audioID], AL_POSITION, position);

	if (checkALError())
		return false;

	// Set source velocity.
	ALfloat velocity[] = { sourceVelocity.x, sourceVelocity.y, sourceVelocity.z };
	alSourcefv(mAudioSources[audioID], AL_VELOCITY, velocity);

	if (checkALError())
		return false;

	// Set source direction.
	//ALfloat direction[] = { sourceDirection.x, sourceDirection.y, sourceDirection.z };
	//alSourcefv(mAudioSources[audioID], AL_DIRECTION, direction);

	//if (checkALError())
	//	return false;

	// Set other parameters.
	alSourcef(mAudioSources[audioID], AL_GAIN, sourceGain);

	if (checkALError())
		return false;

	alSourcef(mAudioSources[audioID], AL_PITCH, sourcePitch);

	if (checkALError())
		return false;

	return true;
}

// ==========================================================================
bool SoundManager::setSourcePosition(ALuint audioID, const Ogre::Vector3& sourcePosition)
{
	ALfloat position[] = { sourcePosition.x, sourcePosition.y, sourcePosition.z };
	alSourcefv(mAudioSources[audioID], AL_POSITION, position);
	if (checkALError())
		return false;
	return true;
}

// ==========================================================================
bool SoundManager::setSourceVelocity(ALuint audioID, const Ogre::Vector3& sourceVelocity)
{
	ALfloat velocity[] = { sourceVelocity.x, sourceVelocity.y, sourceVelocity.z };
	alSourcefv(mAudioSources[audioID], AL_VELOCITY, velocity);
	if (checkALError())
		return false;
	return true;
}

// ==========================================================================
bool SoundManager::setSourceGain(ALuint audioID, const float sourceGain)
{
	alSourcef(mAudioSources[audioID], AL_GAIN, sourceGain);
	if (checkALError())
		return false;
	return true;
}

// ==========================================================================
bool SoundManager::setSourcePitch(ALuint audioID, const float sourcePitch)
{
	alSourcef(mAudioSources[audioID], AL_PITCH, sourcePitch);
	if (checkALError())
		return false;
	return true;
}

// ==========================================================================
bool SoundManager::setLoop(ALuint audioID, bool loop)
{
	alSourcei(mAudioSources[audioID], AL_LOOPING, loop);
	if (checkALError())
		return false;
	return true;
}

// ==========================================================================
bool SoundManager::setListener(const Ogre::Vector3& listererPosition, const Ogre::Vector3& listenerVelocity,
	const Ogre::Vector3& listenerLookAt, const Ogre::Vector3& listenerUp)
{
	// Set listener position.
	ALfloat position[] = { listererPosition.x, listererPosition.y, listererPosition.z };
	alListenerfv(AL_POSITION, position);

	if (checkALError())
		return false;

	// Set listener velocity.
	ALfloat velocity[] = { listenerVelocity.x, listenerVelocity.y, listenerVelocity.z };
	alListenerfv(AL_VELOCITY, velocity);

	if (checkALError())
		return false;
	
	ALfloat orientation[] = {	listenerLookAt.x,	listenerLookAt.y,	listenerLookAt.z,
								listenerUp.x,		listenerUp.y,		listenerUp.z };
	alListenerfv(AL_ORIENTATION, orientation);

	if (checkALError())
		return false;

	return true;
}

// ==========================================================================
bool SoundManager::setListenerPosition(const Ogre::Vector3& listererPosition)
{
	ALfloat position[] = { listererPosition.x, listererPosition.y, listererPosition.z };
	alListenerfv(AL_POSITION, position);
	if (checkALError())
		return false;
	return true;
}

// ==========================================================================
bool SoundManager::setListenerVelocity(const Ogre::Vector3& listenerVelocity)
{
	ALfloat velocity[] = { listenerVelocity.x, listenerVelocity.y, listenerVelocity.z };
	alListenerfv(AL_VELOCITY, velocity);
	if (checkALError())
		return false;
	return true;
}

// ==========================================================================
bool SoundManager::setListenerOrientation(const Ogre::Vector3& listenerLookAt, const Ogre::Vector3& listenerUp)
{
	ALfloat orientation[] = {	listenerLookAt.x,	listenerLookAt.y,	listenerLookAt.z,
								listenerUp.x,		listenerUp.y,		listenerUp.z };
	alListenerfv(AL_ORIENTATION, orientation);
	if (checkALError())
		return false;
	return true;
}